package com.linx.common.mybatis.interceptor;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.linx.common.core.constant.GlobalConstants;
import com.linx.common.core.domain.model.LoginUser;
import com.linx.common.core.exception.ServiceException;
import com.linx.common.core.properties.TenantProperties;
import com.linx.common.core.utils.SpringUtils;
import com.linx.common.core.utils.StringUtils;
import com.linx.common.mybatis.helper.ParkHelper;
import com.linx.common.satoken.utils.LoginHelper;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 数据权限拦截器
 *
 * @author Lion Li
 * @version 3.5.0
 */
@Slf4j
public class PlusDataParkInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {


    private static final String PARK_ID_COLUMN = "park_info_id";
    private static final Long DEFAULT_PARK_ID = 1L; // 默认园区ID
    private static final String DYNAMIC_PARK_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicPark";

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), null));
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {


        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
        //if (sct == SqlCommandType.INSERT ) {
            String parkInfoId = ParkHelper.getparkInfoId();
            if(LoginHelper.isLogin()){
                if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
                    return;
                }
                if (StringUtils.isBlank(parkInfoId)) {
                    log.error("无法获取有效的用户id -> Null");
                    throw new ServiceException("请选择园区后操作!",1024);

                }
                PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
                mpBs.sql(parserMulti(mpBs.sql(), null));
            }


        }
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        String whereSegment = (String) obj;
        this.processSelectBody(select.getSelectBody(), whereSegment);
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach((withItem) -> {
                this.processSelectBody(withItem, whereSegment);
            });
        }
    }
    protected void processInsertSelect(Select selectBody, final String whereSegment) {
        if(selectBody instanceof PlainSelect plainSelect){
            FromItem fromItem = plainSelect.getFromItem();
            if (fromItem instanceof Table) {
                // fixed gitee pulls/141 duplicate update
                processPlainSelect(plainSelect, whereSegment);
                appendSelectItem(plainSelect.getSelectItems());
            } else if (fromItem instanceof Select subSelect) {
                appendSelectItem(plainSelect.getSelectItems());
                processInsertSelect(subSelect, whereSegment);
            }
        } else if(selectBody instanceof ParenthesedSelect parenthesedSelect){
            processInsertSelect(parenthesedSelect.getSelect(), whereSegment);

        }
    }
    protected void appendSelectItem(List<SelectItem<?>> selectItems) {
        if (CollectionUtils.isEmpty(selectItems)) {
            return;
        }
        if (selectItems.size() == 1) {
            SelectItem item = selectItems.get(0);
            Expression expression = item.getExpression();
            if (expression instanceof AllColumns) {
                return;
            }
        }
        selectItems.add(new SelectItem<>(new Column(PARK_ID_COLUMN)));
    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        String parkInfoId = ParkHelper.getparkInfoId();
        TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class);
        List<String> excludes = tenantProperties.getExcludes();
        //判断table是否在excludes中
        String tableName = insert.getTable().getName();

        if (excludes.contains(tableName) || tableName.toLowerCase().startsWith("act_")||tableName.equals("sys_user")) {
            return ;
        }


        List<Column> columns = insert.getColumns();
        if (CollectionUtils.isEmpty(columns)) {
            // 针对不给列名的insert 不处理
            return;
        }
        boolean b = columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(PARK_ID_COLUMN));
        if (b) {
            // 针对已给出租户列的insert 不处理
            return;
        }
        columns.add(new Column(PARK_ID_COLUMN));
        Expression parkInfoIdExpression ;

        parkInfoIdExpression = new StringValue(parkInfoId);



        // fixed gitee pulls/141 duplicate update
        List<UpdateSet> duplicateUpdateColumns = insert.getDuplicateUpdateSets();
        if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
            EqualsTo equalsTo = new EqualsTo();
            equalsTo.setLeftExpression(new StringValue(PARK_ID_COLUMN));
            equalsTo.setRightExpression(parkInfoIdExpression);
            duplicateUpdateColumns.add(new UpdateSet(new Column(PARK_ID_COLUMN), parkInfoIdExpression));
        }

        Select select = insert.getSelect();
        if (select instanceof PlainSelect) { //fix github issue 4998  修复升级到4.5版本的问题
            this.processInsertSelect(select, (String) obj);
        } else if (insert.getValues() != null) {
            // fixed github pull/295
            Values values = insert.getValues();
            ExpressionList<Expression> expressions = (ExpressionList<Expression>) values.getExpressions();
            if (expressions instanceof ParenthesedExpressionList) {
                expressions.addExpression(parkInfoIdExpression);
            } else {
                if (CollectionUtils.isNotEmpty(expressions)) {//fix github issue 4998 jsqlparse 4.5 批量insert ItemsList不是MultiExpressionList 了，需要特殊处理
                    int len = expressions.size();
                    for (int i = 0; i < len; i++) {
                        Expression expression = expressions.get(i);
                        if (expression instanceof Parenthesis) {
                            ExpressionList rowConstructor = new RowConstructor<>()
                                .withExpressions(new ExpressionList<>(((Parenthesis) expression).getExpression(), parkInfoIdExpression));
                            expressions.set(i, rowConstructor);
                        } else if (expression instanceof ParenthesedExpressionList) {
                            ((ParenthesedExpressionList) expression).addExpression(parkInfoIdExpression);
                        } else {
                            expressions.add(parkInfoIdExpression);
                        }
                    }
                } else {
                    expressions.add(parkInfoIdExpression);
                }
            }
        } else {
            throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
        }
    }


    /**
     * update 语句处理
     */
    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        final Table table = update.getTable();


        TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class);
        List<String> excludes = tenantProperties.getExcludes();
        //判断table是否在excludes中
        String tableName = table.getName();

        if (excludes.contains(tableName) || tableName.toLowerCase().startsWith("act_")) {
            return ;
        }



        List<UpdateSet> sets = update.getUpdateSets();
        if (!CollectionUtils.isEmpty(sets)) {
            sets.forEach(us -> us.getValues().forEach(ex -> {
                if (ex instanceof Select) {
                    processSelectBody(((Select) ex), (String) obj);
                }
            }));
        }
        String s = ParkHelper.getparkInfoId();
        if(StringUtils.isNotBlank(s)){
            //给update.getWhere()增加条件
            InExpression inExpression = new InExpression();
            inExpression.setLeftExpression(new Column(PARK_ID_COLUMN));
            List<Long> list = Arrays.stream(s.split(","))
                .map(Long::parseLong)
                .collect(Collectors.toList());
            inExpression.setRightExpression(this.getAliasRightColumn(list));
            update.setWhere(new AndExpression(update.getWhere(), inExpression));

        }

        update.setWhere(this.andExpression(table, update.getWhere(), (String) obj));
    }

    /**
     * delete 语句处理
     */
    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class);
        List<String> excludes = tenantProperties.getExcludes();
        //判断table是否在excludes中
        String tableName = delete.getTable().getName();

        if (excludes.contains(tableName) || tableName.toLowerCase().startsWith("act_")) {
            return ;
        }
        String s = ParkHelper.getparkInfoId();
        if(StringUtils.isNotBlank(s)){
            //给update.getWhere()增加条件
            InExpression inExpression = new InExpression();
            inExpression.setLeftExpression(new Column(PARK_ID_COLUMN));
            List<Long> list = Arrays.stream(s.split(","))
                .map(Long::parseLong)
                .collect(Collectors.toList());
            inExpression.setRightExpression(this.getAliasRightColumn(list));
            delete.setWhere(new AndExpression(delete.getWhere(), inExpression));

        }

        delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere(), (String) obj));
    }


    /**
     * 此处拼接条件
     */
    @Override
    protected Expression builderExpression(Expression currentExpression, List<Table> tables,
                                           final String whereSegment) {
        if (CollectionUtils.isEmpty(tables)) {
            return currentExpression;
        } else {
            Expression dataScopeTableExpression = this.buildTableExpression(tables.get(0), currentExpression,
                    whereSegment);
            if (dataScopeTableExpression == null) {
                return currentExpression;
            } else {
                if (currentExpression == null) {
                    return dataScopeTableExpression;
                } else {
                    return currentExpression instanceof OrExpression ?
                            new AndExpression(new Parenthesis(currentExpression),
                                dataScopeTableExpression) : new AndExpression(currentExpression,
                        dataScopeTableExpression);
                }
            }
        }
    }


    @Override
    public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
//        log.info("where："+where);
//        log.info("whereSegment："+whereSegment);

        return builderExpression(table);
    }

    /**
     * 处理条件
     */
    public Expression builderExpression(Table table) {
        // 判断该表是否在白名单中 ，不在则是业务表 ，需要拼orgId ，在则是系统表，不需要拼orgId

        TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class);
        List<String> excludes = tenantProperties.getExcludes();
        //判断table是否在excludes中
        String tableName = table.getName();

        if (excludes.contains(tableName) || tableName.toLowerCase().startsWith("act_")) {
            return null;
        }

        if(!LoginHelper.isLogin()){
            return null;
        }
        String s = ParkHelper.getparkInfoId();
        // 如果是超级管理员或租户管理员，则不过滤数据
        //if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
        //    return null;
        //}
        //

        InExpression inExpression = new InExpression();

        if(StringUtils.isNotBlank(s)){
            inExpression.setLeftExpression(this.getAliasColumn(table, PARK_ID_COLUMN));
            List<Long> list = Arrays.stream(s.split(","))
                .map(Long::parseLong)
                .collect(Collectors.toList());
            inExpression.setRightExpression(this.getAliasRightColumn(list));
        }else {
            LoginUser currentUser = LoginHelper.getLoginUser();
            List<Long> parkInfoIds = currentUser.getParkInfoIds();
            String parkInfoId = currentUser.getParkInfoId();
            //List<String> parkInfoIds = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(parkInfoIds)) {
                inExpression.setLeftExpression(this.getAliasColumn(table, PARK_ID_COLUMN));
                inExpression.setRightExpression(this.getAliasRightColumn(parkInfoIds));
            } else {
                inExpression.setLeftExpression(this.getAliasColumn(table, PARK_ID_COLUMN));
                List<Long> list = new ArrayList<>();
                list.add(DEFAULT_PARK_ID);
                inExpression.setRightExpression(this.getAliasRightColumn(list));
            }
        }


        return inExpression;
    }

    /**
     * 处理条件后面拼接的值，orgid in ('1','2')
     */
    protected Column getAliasRightColumn(List<Long> list) {
        List<String> stringList = new ArrayList<>();
        for (Long id : list) {
            stringList.add(id.toString());
        }
        return new Column("(" + String.join(",", stringList) + ")");
    }

    /**
     * 表别名设置
     * org_id 或 tableAlias.org_id
     *
     * @param table 表对象
     * @return 字段
     */

    protected Column getAliasColumn(Table table, String columnName) {
        StringBuilder column = new StringBuilder();
        if (table.getAlias() != null) {
            column.append(table.getAlias().getName()).append(".");
        }

        column.append(columnName);
        return new Column(column.toString());
    }
}

